#include "Texture.h"

//==========================================================================
//			Texture or Surface creation/loading
//==========================================================================

Texture::Texture( TextureMgr * textureMgr, DWORD w, DWORD h ) 
	:m_textureMgr(textureMgr)
{
//	Creates GDI compatible surface without initial data.
	D3D11_TEXTURE2D_DESC Desc;
	ZeroMemory( &Desc, sizeof( Desc ) );
	bTex = false;

	bSRV = bRTV = false;
	SRV = NULL;
	RTV = NULL;
	file_name = NULL;
	RefCounter = 1;
	
	has_alpha = true;
	bColorKey = false;
	ColorKey.r = ColorKey.g = ColorKey.b = ColorKey.a = 0.0f;

	Desc.Width = width = w;
	Desc.Height = height = h;
	Desc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;
	Desc.MipLevels = 1;
	Desc.Usage = D3D11_USAGE_DEFAULT;
	Desc.SampleDesc.Count = 1;
	Desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;			//required for GDI compatibility
	Desc.ArraySize = 1;
	Desc.MiscFlags = D3D11_RESOURCE_MISC_GDI_COMPATIBLE;//required for GDI compatibility
	Desc.CPUAccessFlags = 0;
		
	HR( Dev->CreateTexture2D( &Desc, NULL, &Tex ) );
	//HR( Tex->QueryInterface( __uuidof(IDXGISurface1), reinterpret_cast<void**>(&Surf) ) );
#if _DEBUG
	{
		char buffer[200];
		buffer[199] = '\0';
		sprintf(buffer, "[Texture::Texture]%dx%d", w, h);
		if (Tex)
			Tex->SetPrivateData(WKPDID_D3DDebugObjectName, 200, buffer);
	}
#endif
}

Texture::Texture( TextureMgr * textureMgr, DWORD w, DWORD h, void *data ) 
	:m_textureMgr(textureMgr)
{
//	Creates GDI compatible surface from R8G8B8A8 bitmap.
	D3D11_TEXTURE2D_DESC Desc;
	ZeroMemory( &Desc, sizeof( Desc ) );
	Desc.Width = width = w;
	Desc.Height = height = h;
	Desc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;
	Desc.MipLevels = 1;
	Desc.CPUAccessFlags = 0;
	Desc.Usage = D3D11_USAGE_DEFAULT;
	Desc.SampleDesc.Count = 1;
	Desc.SampleDesc.Quality = 0;
	Desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
	Desc.ArraySize = 1;
	Desc.MiscFlags = D3D11_RESOURCE_MISC_GDI_COMPATIBLE;

	D3D11_SUBRESOURCE_DATA SData;
	ZeroMemory( &SData, sizeof( SData ) );
	SData.pSysMem = data;
	SData.SysMemPitch = width*4;

	HR( Dev->CreateTexture2D( &Desc, &SData, &Tex ) );
	//HR( Tex->QueryInterface( __uuidof(IDXGISurface1), reinterpret_cast<void**>(&Surf) ) );
#if _DEBUG
	{
		char buffer[200];
		buffer[199] = '\0';
		sprintf(buffer, "[Texture::Texture, data]%dx%d", w, h);
		if (Tex)
			Tex->SetPrivateData(WKPDID_D3DDebugObjectName, 200, buffer);
	}
#endif
	SRV = NULL;
	RTV = NULL;
	file_name = NULL;
	has_alpha = true;
	bTex = bSRV = bRTV = false;
	RefCounter = 1;
	bColorKey = false;
	ColorKey.r = ColorKey.g = ColorKey.b = ColorKey.a = 0.0f;
}

Texture::Texture( TextureMgr * textureMgr, DWORD w, DWORD h, DXGI_FORMAT Format, const char *fname, bool alpha ) 
	:m_textureMgr(textureMgr)
{
//used for creation of normal map texture from the red channel of bump map.
	D3D11_TEXTURE2D_DESC Desc;
	ZeroMemory( &Desc, sizeof( Desc ) );
	Desc.Width = width = w;
	Desc.Height = height = h;
	Desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
	Desc.MipLevels = cfg->TextureMipMapCount;
	Desc.CPUAccessFlags = 0;
	Desc.Usage = D3D11_USAGE_DEFAULT;
	Desc.SampleDesc.Count = 1;
	Desc.SampleDesc.Quality = 0;
	Desc.Format = Format;
	Desc.ArraySize = 1;
	Desc.MiscFlags = 0;

	HR( Dev->CreateTexture2D( &Desc, NULL, &Tex ) );

	D3D11_SHADER_RESOURCE_VIEW_DESC SDesc;
	ZeroMemory( &SDesc, sizeof( SDesc ));
	SDesc.Format = Format;
	SDesc.Texture2D.MipLevels = 1;
	SDesc.Texture2D.MostDetailedMip = 0;
	SDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;

	HR( Dev->CreateShaderResourceView( Tex, &SDesc, &SRV ) );
	bTex = bSRV = true;

	Surf = NULL;
	RTV = NULL;

	UINT len = strlen( fname );
	if( len ) {
		file_name = new char [len+1];
		strcpy( file_name, fname );
	}

	has_alpha = alpha;	
	bRTV = false;
	RefCounter = 1;
	bColorKey = false;
	ColorKey.r = ColorKey.g = ColorKey.b = ColorKey.a = 0.0f;
#if _DEBUG
	{
		char buffer[200];
		buffer[199] = '\0';
		sprintf(buffer, "[Texture::Texture2]%s", fname);
		if (Tex)
			Tex->SetPrivateData(WKPDID_D3DDebugObjectName, 200, buffer);
		if (SRV)
			SRV->SetPrivateData(WKPDID_D3DDebugObjectName, 200, buffer);
	}
#endif
}

Texture::Texture( TextureMgr * textureMgr, const char *fname, const char *nameWithPath ) 
	: m_textureMgr(textureMgr)
{
//	Loads a texture from file without format modification (if possible).
//	char path[256];
	D3DX11_IMAGE_INFO Info;
	D3DX11_IMAGE_LOAD_INFO LoadInfo;
	HRESULT hr;

	//init:
	Tex = NULL;			//file not found.
	SRV = NULL;
	Surf = NULL;
	RTV = NULL;

	bTex = true;
	has_alpha = bSRV = bRTV = bColorKey = false;
	ColorKey.r = ColorKey.g = ColorKey.b = ColorKey.a = 0.0f;
	RefCounter = 1;

	ZeroMemory( &Info, sizeof(Info) );
	ZeroMemory( &LoadInfo, sizeof(LoadInfo) );

	//load:
	if (nameWithPath == nullptr)
	{
		nameWithPath = fname;
	}
	file_name = NULL;	
	file_name = new char [strlen(fname)+1];
	strcpy( file_name, fname );

	FILE *file;
	BYTE *data;	

	if( !(file = fopen( nameWithPath, "rb" )) )
		return;
	fseek( file, 0, SEEK_END );
	DWORD file_size = ftell( file );
	fseek( file, 0, SEEK_SET );

	data = new BYTE [file_size];
	fread( data, 1, file_size, file );
	fclose( file );

	if( *((DWORD*)data) != MAKEFOURCC( 'D', 'D', 'S', ' ' ) ) {
		delete [ ] data;	//not .dds
		return;
	}

//	DDSURFACEDESC2 *ddesc = (DDSURFACEDESC2*)(data+4);
//	has_alpha = ddesc->ddpfPixelFormat.dwAlphaBitDepth > 0 ? true : false;

	D3DX11GetImageInfoFromMemory( data, file_size, NULL, &Info, &hr );
	if( hr != S_OK ) {		//get image info error
		delete [ ] data;
		return;
	}

	has_alpha = FindAlpha( Info.Format );

	LoadInfo.BindFlags = D3D11_BIND_SHADER_RESOURCE;
	LoadInfo.CpuAccessFlags = 0;
	LoadInfo.Depth = 1;
	LoadInfo.Filter = D3DX11_FILTER_NONE;
	LoadInfo.FirstMipLevel = 0;
	LoadInfo.Format = Info.Format;
	LoadInfo.Height = height = Info.Height;
	LoadInfo.Width = width = Info.Width;
	LoadInfo.Usage = D3D11_USAGE_IMMUTABLE;
	LoadInfo.pSrcInfo = &Info;
	LoadInfo.MiscFlags = 0;
	LoadInfo.MipLevels = cfg->TextureMipMapCount;
	LoadInfo.MipFilter = D3DX11_FILTER_LINEAR;

	D3DX11CreateShaderResourceViewFromMemory( Dev, data, file_size, &LoadInfo, NULL, &SRV, &hr );
	bSRV = true;

	delete [ ] data;

	if( hr != S_OK )	Tex = NULL;			//SRV creation error
	else				SRV->GetResource( (ID3D11Resource**)&Tex );
#if _DEBUG
	if (SUCCEEDED(hr))
	{
		char buffer[200];
		buffer[199] = '\0';
		sprintf(buffer, "[Texture::ctor]%s", fname);
		if (SRV)
			SRV->SetPrivateData(WKPDID_D3DDebugObjectName, 200, buffer);
		if (Tex)
			Tex->SetPrivateData(WKPDID_D3DDebugObjectName, 200, buffer);
	}
#endif
	bTex = true;
	Surf = NULL;
	RTV = NULL;
	bRTV = bColorKey = false;
	ColorKey.r = ColorKey.g = ColorKey.b = ColorKey.a = 0.0f;
	RefCounter = 1;
	return;
}

//==========================================================================
//==========================================================================

Texture::~Texture() {
//Release() is thread safe
	/*if( Tex )	Tex->Release();
	if( Surf )	Surf->Release();
	if( SRV )	SRV->Release();
	if( RTV )	RTV->Release();*/
	if( file_name )	delete [ ] file_name;
}

void Texture::GetSize( DWORD *w, DWORD *h ) {
	*w = width;
	*h = height;
}

void Texture::SetColorKey( DWORD ckey ) {
//color key can be used in blit for transparency
	ColorKey = D3DXCOLOR( ckey );
	bColorKey = true;
}

bool Texture::CreateGDIShadowSurface()
{
	if (!Tex)
		return false;
	if (m_gdiTex)
		return true;
	D3D11_TEXTURE2D_DESC desc;
	Tex->GetDesc(&desc);
	auto hr = Dev->CreateTexture2D(&desc, nullptr, &m_gdiTex);
	if (FAILED(hr))
		return false;
	hr = m_gdiTex.QueryInterface(&Surf);
	if (FAILED(hr))
		return false;
	D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
	ZeroMemory(&srvDesc, sizeof(D3D11_SHADER_RESOURCE_VIEW_DESC));
	srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
	srvDesc.Format = desc.Format;
	srvDesc.Texture2D.MipLevels = 1;
	hr = Dev->CreateShaderResourceView(m_gdiTex, &srvDesc, &m_gdiTexSRV);
	if (FAILED(hr))
		return false;
	D3D11_RENDER_TARGET_VIEW_DESC rtvDesc;
	ZeroMemory(&rtvDesc, sizeof(D3D11_RENDER_TARGET_VIEW_DESC));
	rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
	rtvDesc.Format = desc.Format;
	rtvDesc.Texture2D.MipSlice = 0;
	hr = Dev->CreateRenderTargetView(m_gdiTex, &rtvDesc, &m_gdiTexRTV);
	if (FAILED(hr))
		return false;
	return true;
}

HDC Texture::GetDC() {
/*	returns GDI DC for surface	(thread-unsafe)
	works only for B8G8R8A8 textures
	slow
*/
	if (!CreateGDIShadowSurface())
		return nullptr;
	//opCode = 1 - copy to result and set alpha to 1
	m_textureMgr->Execute2DOperation(GetSRV(), &m_gdiTexRTV, width, height, 1);
	HDC hdc;
	HRESULT hr;
	hr = Surf->GetDC( FALSE, &hdc );
	if( hr == S_OK )
		return hdc;
	else {
		oapiWriteLog( "GetDC() failed");
		return NULL;
	}
}

void Texture::ReleaseDC() {
//releases GDI DC;
	Surf->ReleaseDC( NULL );
	m_gdiUsed = true;
	//opCode = 2 - copy to result where alpha is 0
	m_textureMgr->Execute2DOperation(&m_gdiTexSRV, GetRTV(), width, height, 2);
}

ID3D11ShaderResourceView **Texture::CreateSRV() {
//creates SRV for surface
	D3D11_SHADER_RESOURCE_VIEW_DESC SDesc;

	ZeroMemory( &SDesc, sizeof( SDesc ));
	SDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
	SDesc.Texture2D.MipLevels = 1;
	SDesc.Texture2D.MostDetailedMip = 0;
	SDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;

	HR( Dev->CreateShaderResourceView( Tex, &SDesc, &SRV ) );
	/*auto hr = Dev->CreateShaderResourceView( Tex, &SDesc, &SRV );
	D3D11_TEXTURE2D_DESC tDesc;
	if (FAILED(hr))
	{
		Tex->GetDesc(&tDesc);
	}*/
	bSRV = true;
	return &SRV;
}

ID3D11RenderTargetView **Texture::CreateRTV() {
	if( bTex )	return NULL;	//RTV cannot be created for a texture

	D3D11_RENDER_TARGET_VIEW_DESC RDesc;

	ZeroMemory( &RDesc, sizeof( RDesc ) );
	RDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
	RDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
	RDesc.Texture2D.MipSlice = 0;

	HR( Dev->CreateRenderTargetView( Tex, &RDesc, &RTV ) );
	bRTV = true;
	return &RTV;
}

bool Texture::FindAlpha( DXGI_FORMAT format ) {
	switch( format ) {
	case DXGI_FORMAT_BC2_UNORM:
	case DXGI_FORMAT_BC3_UNORM:
	case DXGI_FORMAT_BC4_UNORM:
	case DXGI_FORMAT_BC5_UNORM:
	case DXGI_FORMAT_R8G8B8A8_UNORM:
	case DXGI_FORMAT_B5G5R5A1_UNORM:
	case DXGI_FORMAT_B8G8R8A8_UNORM:
	case DXGI_FORMAT_B8G8R8X8_UNORM:
		return true;
	}
	return false;
}